﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Windows.Forms;
using Noris.Tools.TraceVisualiser.Interface;
using Noris.Tools.TraceVisualiser.TraceData;
using Noris.Tools.TraceVisualiser.Support;
using Noris.Tools.TraceVisualiser.Components;
using Noris.Tools.TraceVisualiser.Forms;

namespace Noris.Tools.TraceVisualiser.Green.AplServer
{
    /// <summary>
    /// Poskytovatel vyhledávání (ISearchProvider).
    /// Kontextová funkce, která umožní převzít vlastnosti elementu a vložit je do vyhledávacího okna.
    /// </summary>
    public class SearchProviderAplServer : ISearchProvider, ISearchPatternProvider
    {
        #region Konstruktor
        public SearchProviderAplServer()
        {
            this._PrepareFormatters();
        }
        #endregion
        #region ISearchProvider + IPlugin Members
        bool IPlugin.Active { get { return true; } }
        string ISearchProvider.ProviderName { get { return "Aplikační trace"; } }
        System.Windows.Forms.Control ISearchProvider.CreateSearchControl() { return new SearchPanel(); }
        #endregion
        #region ISearchPatternProvider Members
        /// <summary>
        /// Metoda zjistí, zda tento provider může být aktivován funkcí "Hledej podobné události".
        /// Provider se podívá, kterou událost uživatel vybral (kliknul na ní), a určí zda je schopen podle události naplnit svůj control.
        /// Pokud ne, vrací hodnotu 0 (nebo záporné).
        /// Pokud ano, rozhodne se, s jakou prioritou se k události postaví = vrátí číslo vyjadřující jeho vhodnost.
        /// Čím nižší číslo, tím vhodnější je tento provider.
        /// Implicitní searchery pro "svoje" události vracejí hodnoty 100-199.
        /// Expertní searchery by měly vracet hodnoty 10-99.
        /// Uživatelské searchery pak hodnoty 1-9.
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        float ISearchPatternProvider.CanSearchByPattern(SearchProviderPatternArgs args)
        {
            EventItem eventItem = args.EventInfo.EventItem as EventItem;
            return ((eventItem == null) ? 0f : 199f);
        }
        /// <summary>
        /// Provider vytvoří data (XElement), která odpovídají události předané v argumentu (args.EventInfo) = element v grafu.
        /// Tato metoda se volá v průběhu výkonu funkce "Hledej podobné události", 
        /// poté kdy tento provider "vyhrál" v soutěži o nejvhodnějšího providera 
        /// (v metodě ISearchPatternProvider.CanSearchByPattern() vrátil nejnižší hodnotu větší než 0).
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        XElement ISearchPatternProvider.CreatePresetData(SearchProviderPatternArgs args)
        {
            EventItem eventItem = args.EventInfo.EventItem as EventItem;
            return ((eventItem == null) ? null : SearchPanel.CreatePresetData(eventItem));
        }
        #endregion
        #region Search prepare and detect
        /// <summary>
        /// Provider se připraví ke hledání dat.
        /// Dostává referenci na dokument a na objekt (Control), který obsahuje zadání pro hledání.
        /// Tento Control je identický s tím objektem, který provider sám vytvořil v metodě CreateSearchControl().
        /// V této metodě může dojít k chybě. Chyba je odchycena vně providera a ohlášena, pak se hledání nespustí.
        /// Provider může specifikovat, v kterých oblastech se má hledat (v událostech načtených v paměti: args.EnableSearchProcesses, a v dosud nenačtených trace souborech: args.EnableSearchTraceFiles).
        /// Implicitně jsou obě možnosti true.
        /// </summary>
        /// <param name="args"></param>
        void ISearchProvider.PrepareToSearch(SearchProviderPrepareArgs args)
        {
            SearchPanel panel = args.SearchControl as SearchPanel;
            if (panel == null)
                throw new ArgumentException("Do vyhledávače " + this.GetType().Namespace + "." + this.GetType().Name + " nebyl předán správný objekt Control (do metody ISearchProvider.PrepareToSearch()).");

            this._PrepareTest(args, panel);
        }
        /// <summary>
        /// Provider zde může určit, zda jej zajímají data daného procesu (načtená v paměti).
        /// Proces je skupina událostí, definovaná readerem trace souboru. U trace aplikačního serveru je to například SessionID.
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        bool ISearchProvider.CanSearchInProcess(SearchProviderDetectProcessArgs args)
        {
            return (args.Process.Server.TraceDataType == NorisAplConstants.TraceType);
        }
        /// <summary>
        /// Provider zde může určit, zda jej zajímají data daného trace souboru (dosud nenačtený).
        /// U souboru je již určeno, o jaký soubor jde a kdo (který reader) jej bude načítat.
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        bool ISearchProvider.CanSearchInTraceFile(SearchProviderDetectTraceArgs args)
        {
            return (args.Trace.TraceType == NorisAplConstants.TraceType);
        }
        /// <summary>
        /// Provider zjistí, zda událost popisovaná v args.EventItem vyhovuje podmínkám vyhledání.
        /// Tato metoda je volána pro události již načtené v paměti.
        /// Pokud ano, vrátí true. Pro událost bude vytvořen řádek v seznamu výsledků, informace o události jsou systému známy.
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        bool ISearchProvider.TestEvent(SearchProviderTestEventArgs args)
        {
            EventItem eventItem = args.EventItem as EventItem;
            if (eventItem == null) return false;
            return this._TestEvent(args, eventItem);
        }
        /// <summary>
        /// Provider zjistí, zda událost popisovaná v args.Data vyhovuje podmínkám vyhledání.
        /// Tato metoda je volána pro události načtené ze souboru (v závislosti na konkrétním TraceReaderu).
        /// Pokud ano, vrátí objekt ITextGraphPointer = pointer na místo v grafu + textové popisky.
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        ITextGraphPointer ISearchProvider.TestObject(SearchProviderTestTraceDataArgs args)
        {
            string[] items = args.Data as string[];
            if (items == null || items.Length < 19) return null;
            return this._TestObject(args, items);
        }
        #endregion
        #region Příprava pro hledání, proměnné, převody textu na čísla
        /// <summary>
        /// Připraví se na testování = opíše do sebe parametry hledání.
        /// Vrací true = OK, false = nejsou zadané dostatečné parametry hledání.
        /// </summary>
        /// <param name="panel"></param>
        private void _PrepareTest(SearchProviderPrepareArgs args, SearchPanel panel)
        {
            string keyword = panel.ParamKeyword;
            keyword = (keyword == null ? "" : keyword.Trim());
            switch (keyword)
            {
                case SearchPanel.KEYW_COWLEY:
                    this._TestIWE = "I";
                    this._TestKeyword = new SearchTestItemString(keyword);
                    this._TestCowleyClass = _PrepareInt32(panel.ParamType);
                    this._TestCowleyName = _PrepareString(panel.ParamMethod);
                    break;
                case SearchPanel.KEYW_INSTANCE:
                    this._TestIWE = "I";
                    this._TestKeyword = new SearchTestItemString(keyword);
                    this._TestInstanceType = _PrepareString(panel.ParamType);
                    this._TestInstanceMethod = _PrepareString(panel.ParamMethod);
                    break;
                case SearchPanel.KEYW_SQL:
                    this._TestIWE = "I";
                    this._TestKeyword = new SearchTestItemString(keyword);
                    this._TestSqlSPID = _PrepareInt32(panel.ParamType);
                    this._TestSqlText = _PrepareString(panel.ParamMethod);
                    break;
                case SearchPanel.KEYW_IOM:
                    this._TestIWE = "I";
                    this._TestKeyword = new SearchTestItemString(keyword);
                    this._TestIomClass = _PrepareInt32(panel.ParamType);
                    this._TestIomNumber = _PrepareInt32(panel.ParamMethod);
                    break;
                case SearchPanel.KEYW_WARNING:
                    this._TestIWE = "W";
                    this._TestKeyword = new SearchTestItemString();        // KEYWORD jako takový se bude testovat jako součást Type
                    this._TestSystem1 = _PrepareString(panel.ParamType);
                    this._TestSystem2 = _PrepareString(panel.ParamMethod);
                    break;
                case SearchPanel.KEYW_ERROR:
                    this._TestIWE = "E";
                    this._TestKeyword = new SearchTestItemString();        // KEYWORD jako takový se testovat nebude (vždy obsahuje ERROR)
                    this._TestSystem1 = _PrepareString(panel.ParamType);
                    this._TestSystem2 = _PrepareString(panel.ParamMethod);
                    break;
                case SearchPanel.KEYW_DEADLOCK:
                    this._TestIWE = "D";
                    this._TestKeyword = new SearchTestItemString();        // KEYWORD jako takový se testovat nebude (vždy obsahuje ERROR)
                    this._TestSystem1 = _PrepareString(panel.ParamType);
                    this._TestSystem2 = _PrepareString(panel.ParamMethod);
                    break;
                default:
                    this._TestIWE = "I";
                    this._TestKeyword = new SearchTestItemString(keyword);
                    this._TestType = _PrepareString(panel.ParamType);
                    this._TestMethod = _PrepareString(panel.ParamMethod);
                    break;
            }

            this._TestTimeMin = this._PrepareDouble(panel.ParamTimeMin);
            this._TestTimeMax = this._PrepareDouble(panel.ParamTimeMax);

            args.EnableSearchProcesses = true;
            args.EnableSearchTraceFiles = (panel.ParamSearchInFiles ? SearchProviderScanTraceFileMode.ScanOnlyNoProcessedFiles : SearchProviderScanTraceFileMode.NoSearch);
        }
        private string _TestIWE;
        private SearchTestItemString _TestKeyword;
        private SearchTestItemString _TestType;
        private SearchTestItemString _TestMethod;
        private SearchTestItemString _TestInstanceType;
        private SearchTestItemString _TestInstanceMethod;
        private SearchTestItem<int> _TestIomClass;
        private SearchTestItem<int> _TestIomNumber;
        private SearchTestItem<int> _TestCowleyClass;
        private SearchTestItemString _TestCowleyName;
        private SearchTestItemString _TestSystem1;
        private SearchTestItemString _TestSystem2;
        private SearchTestItem<int> _TestSqlSPID;
        private SearchTestItemString _TestSqlText;
        private SearchTestItem<double> _TestTimeMin;
        private SearchTestItem<double> _TestTimeMax;
        private SearchTestItemString _PrepareString(string text)
        {
            if (String.IsNullOrEmpty(text)) return new SearchTestItemString();
            return new SearchTestItemString(text);
        }
        private SearchTestItem<double> _PrepareDouble(string text)
        {
            double? value = _GetDouble(text);
            if (value.HasValue)
                return new SearchTestItem<double>(value.Value);
            return new SearchTestItem<double>();
        }
        private SearchTestItem<decimal> _PrepareDecimal(string text)
        {
            decimal? value = _GetDecimal(text);
            if (value.HasValue)
                return new SearchTestItem<decimal>(value.Value);
            return new SearchTestItem<decimal>();
        }
        private SearchTestItem<Int32> _PrepareInt32(string text)
        {
            Int32? value = _GetInt32(text);
            if (value.HasValue)
                return new SearchTestItem<int>(value.Value);
            return new SearchTestItem<int>();
        }
        private double? _GetDouble(string text)
        {
            if (String.IsNullOrEmpty(text)) return null;
            string txt = text.Replace(',', '.');
            double value;
            if (Double.TryParse(txt, System.Globalization.NumberStyles.Number, this._Nfi, out value))
                return value;
            return null;
        }
        private decimal? _GetDecimal(string text)
        {
            if (String.IsNullOrEmpty(text)) return null;
            string txt = text.Replace(',', '.');
            decimal value;
            if (Decimal.TryParse(txt, System.Globalization.NumberStyles.Number, this._Nfi, out value))
                return value;
            return null;
        }
        private Int32? _GetInt32(string text)
        {
            if (String.IsNullOrEmpty(text)) return null;
            string txt = text.Replace(',', '.');
            Int32 value;
            if (Int32.TryParse(txt, System.Globalization.NumberStyles.Integer, this._Nfi, out value))
                return value;
            return null;
        }
        private void _PrepareFormatters()
        {
            this._Nfi = new System.Globalization.NumberFormatInfo() { NumberDecimalSeparator = ".", NumberGroupSeparator = "" };
        }
        private System.Globalization.NumberFormatInfo _Nfi;
        #endregion
        #region Testování parametrů pro objekty načtené v paměti
        /// <summary>
        /// Otestuje danou událost, zda odpovídá filtru.
        /// </summary>
        /// <param name="args"></param>
        /// <param name="eventItem"></param>
        /// <returns></returns>
        private bool _TestEvent(SearchProviderTestEventArgs args, EventItem eventItem)
        {
            if (eventItem.MergedOut) return false;

            switch (eventItem.Level)
            {
                case LineIWE.Information:
                    if (this._TestIWE != "I") return false;
                    string keyword = eventItem.Keyword;
                    if (!this._TestKeyword.TestContain(keyword)) return false;
                    double seconds = Math.Round(eventItem.ElementTime.Seconds);
                    if (!this._TestTimeMin.TestMin(seconds)) return false;
                    if (!this._TestTimeMax.TestMax(seconds)) return false;

                    EventCowleyItem cwl;
                    EventPairItem pair;
                    EventSqlItem sql;
                    keyword = (keyword == null ? "" : keyword.Trim().ToUpper());
                    switch (keyword)
                    {
                        case SearchPanel.KEYW_COWLEY:
                            cwl = eventItem as EventCowleyItem;
                            if (cwl == null) return false;
                            if (!this._TestCowleyClass.TestEqual(cwl.CowleyClassNumber)) return false;
                            if (!(this._TestCowleyName.TestContain(cwl.CowleyActionName) || this._TestCowleyName.TestContain(cwl.CowleyFunctionNumber.ToString()) || this._TestCowleyName.TestContain(cwl.CowleyTypeFullName))) return false;
                            return true;
                        case SearchPanel.KEYW_INSTANCE:
                            pair = eventItem as EventPairItem;
                            if (pair == null) return false;
                            if (!this._TestInstanceType.TestContain(pair.Type)) return false;
                            if (!this._TestInstanceMethod.TestContain(pair.Method)) return false;
                            return true;
                        case SearchPanel.KEYW_SQL:
                            sql = eventItem as EventSqlItem;
                            if (sql == null) return false;
                            if (!this._TestSqlSPID.TestEqual(sql.SqlPId)) return false;
                            if (!this._TestSqlText.TestContain(sql.SqlCommand)) return false;
                            return true;
                        case SearchPanel.KEYW_IOM:
                            pair = eventItem as EventPairItem;
                            if (pair == null) return false;
                            break;
                        default:
                            if (!this._TestType.TestContain(eventItem.Type)) return false;
                            if (!this._TestMethod.TestContain(eventItem.Method)) return false;
                            break;
                    }
                    return true;

                case LineIWE.Warning:
                    if (this._TestIWE != "W") return false;
                    if (!this._TestEventSysWarning(eventItem)) return false;
                    return true;

                case LineIWE.Error:
                    if (this._TestIWE == "E")
                    {
                        if (!this._TestEventSysError(eventItem)) return false;
                        return true;
                    }
                    else if (this._TestIWE == "D")
                    {
                        if (eventItem.StatusInfo != StatusInfoType.Deadlock) return false;
                        if (!this._TestEventSysError(eventItem)) return false;
                        return true;
                    }
                    return false;
                case LineIWE.Debug:
                    return true;
            }

            return false;
        }
        /// <summary>
        /// Testuje systémové události - jejich pole Keyword+Type+Method, a User0 a User1 ÷ nn
        /// </summary>
        /// <param name="eventItem"></param>
        /// <returns></returns>
        private bool _TestEventSysWarning(EventItem eventItem)
        {
            int userLength = eventItem.UserLength;

            string eol = Environment.NewLine;
            if (this._TestSystem1.IsTested)
            {
                string keyw = eventItem.Keyword + eol + eventItem.Type + eol + eventItem.Method;
                if (!this._TestSystem1.TestContain(keyw)) return false;
            }

            if (this._TestSystem2.IsTested)
            {
                if (userLength < 1) return false;
                string user = "";
                for (int i = 0; i < userLength; i++)
                    user += eventItem.UserData(i) + eol;
                if (!this._TestSystem2.TestContain(user)) return false;
            }
            return true;
        }
        /// <summary>
        /// Testuje systémové události - jejich pole User0 a User1 ÷ nn
        /// </summary>
        /// <param name="eventItem"></param>
        /// <returns></returns>
        private bool _TestEventSysError(EventItem eventItem)
        {
            int userLength = eventItem.UserLength;

            string eol = Environment.NewLine;
            if (this._TestSystem1.IsTested)
            {
                if (userLength < 1) return false;
                string user0 = eventItem.UserData(0);
                if (!this._TestSystem1.TestContain(user0)) return false;
            }

            if (this._TestSystem2.IsTested)
            {
                string user = "";
                for (int i = 1; i < userLength; i++)
                    user += eventItem.UserData(i) + eol;
                if (!this._TestSystem2.TestContain(user)) return false;
            }
            return true;
        }
        #endregion
        #region Testování parametrů pro objekty načtené ze souboru
        /// <summary>
        /// Otestuje data, zda vyhovují data načtená ze souboru aktuálním parametrům.
        /// </summary>
        /// <param name="args"></param>
        /// <param name="items"></param>
        /// <returns></returns>
        private ITextGraphPointer _TestObject(SearchProviderTestTraceDataArgs args, string[] items)
        {
            int length = items.Length;
            if (length < 19) return null;
            int userCount = length - 19;

            string iwe = items[6];
            string bse = items[7];
            switch (iwe)
            {
                case "I":
                    if (this._TestIWE != "I") return null;
                    string keyword = items[18];                                           // KEYWORD
                    if (!this._TestKeyword.TestContain(keyword)) return null;
                    double seconds = 0d;
                    if (bse == "E")
                    {
                        int? milisec = _GetInt32(items[9]);
                        if (milisec.HasValue && milisec.Value > 0)
                            seconds = (double)(milisec.Value) / 1000d;
                        if (!this._TestTimeMin.TestMin(seconds)) return null;
                        if (!this._TestTimeMax.TestMax(seconds)) return null;
                    }

                    Int32? classNumber;
                    keyword = (keyword == null ? "" : keyword.Trim().ToUpper());

                    switch (keyword)
                    {
                        case SearchPanel.KEYW_COWLEY:
                            if (bse != "E") return null;
                            if (items[17] != "_Run1_nonvisualPart") return null;               // Jedině tento řádek budeme zobrazovat
                            if (userCount < 2) return null;
                            classNumber = _GetInt32(items[19]);                                // Function class number
                            if (!classNumber.HasValue) return null;
                            if (!this._TestCowleyClass.TestEqual(classNumber.Value)) return null;
                            if (!(this._TestCowleyName.TestContain(items[20]))) return null;   // Function name (action)
                            return this._CreateTextPointerForCowley(args, items, seconds);
                        case SearchPanel.KEYW_INSTANCE:
                            if (bse != "E") return null;
                            if (!this._TestInstanceType.TestContain(items[16])) return null;
                            if (!this._TestInstanceMethod.TestContain(items[17])) return null;
                            return this._CreateTextPointerForInstance(args, items, seconds);
                        case SearchPanel.KEYW_SQL:
                            // SQL událost má zvláštní zacházení, protože klíčové údaje nejsou v jednom řádku (text SQL příkazu a SPID je v řádku Begin, a čas je v řádku v End).
                            // Pokud tedy mám detekovat text, řeším to v řádku Begin:
                            bool found = false;
                            string sqlText = null;
                            if (bse == "B")
                            {   // Na začátku hledám SQL text a SPID (musím mít dva user údaje):
                                if (userCount < 2) return null;
                                if (!this._TestSqlText.TestContain(items[20])) return null;    // Pokud text testujeme, ale nevyhovuje => končíme.
                                if (this._TestSqlSPID.IsTested)
                                {   // Pokud máme testovat SPID:
                                    int spid;
                                    if (!Int32.TryParse(items[19], out spid))
                                        return null;
                                    if (!this._TestSqlSPID.TestEqual(spid))
                                        return null;
                                }
                                // Řádek nám vyhovuje (SQL a SPID), anebo údaje ani netestuejeme.
                                // Sestavím jeho text (SQL + params):
                                sqlText = TraceSupport.ShortSqlCommandText(items[20], 150, 3);
                                if (userCount >= 3)
                                    sqlText += Environment.NewLine + "params: " + items[21];

                                // Ať už chceme nebo nechceme (testovat délku události: if (!this._TestTimeMin.IsTested && !this._TestTimeMax.IsTested)),
                                // tak musíme událost do resultu dát až po nalezení řádku End.
                                // Proč? protože jinak by se do resultu dostal výsledek už teď, kdy ještě nemáme informaci o jeho času (délka).
                                // To vadí? Ano, jednal doba trvání je důležitá pro uživatele (aby ji viděl),
                                //    a jednak pro TraceVisualiser, když pak na result dáme doubleclick, tak je lepší zazoomovat čas na celou událost (a k tomu je třeba znát její délku).
                                GID eventGId = this._FindEventGid(items);
                                if (!eventGId.IsEmpty)
                                    this._SqlItemDict.Add(eventGId, sqlText);              // Pod tento klíč si zapamatujeme text, bude se vypisovat až se otestuje čas (někdy příště spadneme do větve if (bse == "E")).
                            }
                            else if (bse == "E")
                            {   // Na konci ověřujeme čas události. Ale to už je ověřeno na začátku celé metody (pro řádek bse == "E"), anebo se čas netestuje, a jsme na konci.
                                GID eventGId = this._FindEventGid(items);
                                if (!eventGId.IsEmpty)
                                    found = this._SqlItemDict.TryGetValue(eventGId, out sqlText);
                            }
                            if (!found) return null;

                            // Pokud je found == true, našli jsme co jsme hledali, a vytvoříme z toho událost:
                            return this._CreateTextPointerForSql(args, items, seconds, sqlText);

                        case SearchPanel.KEYW_IOM:
                            // this._TestIomClass = _PrepareInt32(panel.ParamType);
                            // this._TestIomNumber = _PrepareInt32(panel.ParamMethod);
                            return null;
                        default:
                            if (!this._TestType.TestContain(items[16])) return null;
                            if (!this._TestMethod.TestContain(items[17])) return null;
                            return this._CreateTextPointerGeneral(args, items, seconds);
                    }

                case "W":
                    if (this._TestIWE != "W") return null;
                    if (bse != "S") return null;
                    if (!this._TestObjectSysWarning(items, userCount)) return null;
                    return this._CreateTextPointerSystem(args, items, "Warning");

                case "E":
                    if (bse != "S") return null;
                    if (this._TestIWE == "E")
                    {
                        if (!this._TestObjectSysError(items, userCount)) return null;
                        bool isDeadlock = this._TestObjectDeadlock(items, userCount);
                        string info = (isDeadlock ? "Deadlock" : "Error");
                        return this._CreateTextPointerSystem(args, items, info);
                    }
                    else if (this._TestIWE == "D")
                    {
                        if (!this._TestObjectDeadlock(items, userCount)) return null;
                        if (!this._TestObjectSysError(items, userCount)) return null;
                        return this._CreateTextPointerSystem(args, items, "Deadlock");
                    }
                    return null;

            }

            return null;
        }
        /// <summary>
        /// Testuje systémovou událost, zda podle polí User0 a User1 jde o deadlock
        /// </summary>
        /// <param name="items"></param>
        /// <param name="userLength"></param>
        /// <returns></returns>
        private bool _TestObjectDeadlock(string[] items, int userLength)
        {
            string user0 = (userLength >= 1 ? items[19] : String.Empty);
            string user1 = (userLength >= 2 ? items[20] : String.Empty);
            return AplServer.TraceItem.IsDeadlockByUser(user0, user1);
        }
        /// <summary>
        /// Testuje systémové události - jejich pole Keyword+Type+Method, a User0 a User1 ÷ nn
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private bool _TestObjectSysWarning(string[] items, int userLength)
        {
            string eol = Environment.NewLine;
            if (this._TestSystem1.IsTested)
            {
                string keyw = items[18] + eol + items[16] + eol + items[17];
                if (!this._TestSystem1.TestContain(keyw)) return false;
            }
            if (this._TestSystem2.IsTested)
            {
                if (userLength < 1) return false;
                string user = "";
                for (int i = 0; i < userLength; i++)
                    user += items[i + 19] + eol;
                if (!this._TestSystem2.TestContain(user)) return false;
            }
            return true;
        }
        /// <summary>
        /// Testuje systémové události - jejich pole User0 a User1 ÷ nn
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private bool _TestObjectSysError(string[] items, int userLength)
        {
            string eol = Environment.NewLine;
            if (this._TestSystem1.IsTested)
            {
                if (userLength < 1) return false;
                string user0 = items[19];
                if (!this._TestSystem1.TestContain(user0)) return false;
            }
            if (this._TestSystem2.IsTested)
            {
                if (userLength < 2) return false;
                string user = "";
                for (int i = 1; i < userLength; i++)
                    user += items[i + 19] + eol;
                if (!this._TestSystem2.TestContain(user)) return false;
            }
            return true;
        }
        private Dictionary<GID, string> _SqlItemDict
        {
            get
            {
                if (this.__SqlItemDict == null)
                    this.__SqlItemDict = new Dictionary<GID, string>();
                return this.__SqlItemDict;
            }
        }
        private Dictionary<GID, string> __SqlItemDict;
        /// <summary>
        /// Pro danou událost (data z trace) vytvoří GID: Class odpovídá Session, Record = BEDept.
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private GID _FindEventGid(string[] items)
        {
            int beDepth;
            if (!Int32.TryParse(items[8], out beDepth))
                return GID.Empty;

            int sid = this._FindSession(items[12]);
            return new GID(sid, beDepth);                  // Klíč události = Session + BEDepth (párový numerický klíč):
        }
        /// <summary>
        /// Najde a vrátí číslo Session podle jejího názvu
        /// </summary>
        /// <param name="sessionId"></param>
        /// <returns></returns>
        private int _FindSession(string sessionId)
        {
            if (__SessionDict == null)
                __SessionDict = new Dictionary<string, int>();
            if (String.IsNullOrEmpty(sessionId)) return 0;
            int id;
            if (!__SessionDict.TryGetValue(sessionId, out id))
            {
                id = __SessionDict.Count + 1;
                __SessionDict.Add(sessionId, id);
            }
            return id;
        }
        private Dictionary<string, int> __SessionDict;

        private ITextGraphPointer _CreateTextPointerGeneral(SearchProviderTestTraceDataArgs args, string[] items, double seconds)
        {
            TraceDocTextPointer pointer = new TraceDocTextPointer();
            _FillTextPointerCommon(pointer, args, items, seconds);
            pointer.Text = items[16] + "." + items[17];
            return pointer;
        }
        private ITextGraphPointer _CreateTextPointerSystem(SearchProviderTestTraceDataArgs args, string[] items, string info)
        {
            TraceDocTextPointer pointer = new TraceDocTextPointer();
            _FillTextPointerCommon(pointer, args, items, 0d);
            pointer.Info = info;
            if (items[06] == "E" && items.Length >= 20)      // Error: 
                pointer.Text = items[19];                    //  zobrazím [19] = User0
            else
                pointer.Text = items[16] + "." + items[17];  // Jinak: Type.Method
            _FillTextPointerAddUserTooltip(pointer, args, items);
            return pointer;
        }
        private ITextGraphPointer _CreateTextPointerForCowley(SearchProviderTestTraceDataArgs args, string[] items, double seconds)
        {
            TraceDocTextPointer pointer = new TraceDocTextPointer();
            _FillTextPointerCommon(pointer, args, items, seconds);
            pointer.Text = "Cowley " + items[20] + " (class=" + items[19] + ")";
            return pointer;
        }
        private ITextGraphPointer _CreateTextPointerForInstance(SearchProviderTestTraceDataArgs args, string[] items, double seconds)
        {
            TraceDocTextPointer pointer = new TraceDocTextPointer();
            _FillTextPointerCommon(pointer, args, items, seconds);
            pointer.Text = "NrsInstance " + items[16] + "." + items[17];
            _FillTextPointerAddUserTooltip(pointer, args, items);
            return pointer;
        }
        private ITextGraphPointer _CreateTextPointerForSql(SearchProviderTestTraceDataArgs args, string[] items, double seconds, string sqlText)
        {
            TraceDocTextPointer pointer = new TraceDocTextPointer();
            _FillTextPointerCommon(pointer, args, items, seconds);
            string eol = Environment.NewLine;
            pointer.TooltipText += eol + sqlText;
            pointer.Text = sqlText;
            return pointer;
        }
        /// <summary>
        /// Naplní základní společné informace do pointeru: Time, ExpandedNodes, FirstVisibleNode, FirstVisiblePixel.
        /// </summary>
        /// <param name="pointer"></param>
        /// <param name="args"></param>
        /// <param name="items"></param>
        /// <param name="seconds"></param>
        private void _FillTextPointerCommon(TraceDocTextPointer pointer, SearchProviderTestTraceDataArgs args, string[] items, double seconds)
        {
            DateTime time = MainInfo.ConvertDateTime(items[0] + ";" + items[1]);
            TimeRange timeRange = new TimeRange(time, time);
            string bse = items[7];
            if (bse == "E" && seconds > 0d)
                timeRange.Begin = time.AddSeconds(-seconds);
            pointer.Time = TimeAxisCalcCls.AlignTimeToAxis(timeRange, true);

            pointer.Info = timeRange.Seconds.ToString("### ##0.000").Trim();
            string eol = Environment.NewLine;
            pointer.TooltipText = "Time: " + timeRange.StringBeginAndSecs + eol +
                "Trace: " + args.Trace.FileDirName + eol +
                eol +
                "Login: " + items[11].ToStringNull() + eol +
                "Keyword: " + items[18].ToStringNull() + eol +
                "Type: " + items[16].ToStringNull() + eol +
                "Method: " + items[17].ToStringNull() + eol;

            string nodeUserKey = TraceReader.CreateNodeUserKey(items[11], items[12], false);
            pointer.ExpandedNodes.Add(NorisAplConstants.NodeUser);
            pointer.ExpandedNodes.Add(nodeUserKey);
            pointer.FirstVisibleNode = nodeUserKey;
            pointer.FirstVisiblePixel = 0;
            pointer.RowFilter = null;
        }
        private void _FillTextPointerAddUserTooltip(TraceDocTextPointer pointer, SearchProviderTestTraceDataArgs args, string[] items)
        {
            int length = items.Length;
            if (length < 20) return;
            int userLength = length - 19;

            string eol = Environment.NewLine;
            string userText = "";
            for (int i = 0; i < userLength; i++)
                userText += eol + "User" + i.ToString() + ": " + items[i + 19];

            pointer.TooltipText += userText;
        }
        #endregion
    }
    /// <summary>
    /// Panel pro zadání vyhledávacích parametrů
    /// </summary>
    internal class SearchPanel : DPanel, IDataPersistent
    {
        #region Konstrukce
        internal SearchPanel()
        {
            this.InitializeComponents();
        }
        private void InitializeComponents()
        {
            this.BorderStyle = BorderStyle.None;
            this.Padding = new Padding(0);

            int xl = 5;
            int xc = 0;
            int y = 0;
            int w = 180;
            int wh = 85;
            int xr = 95;
            int ly = -2;
            int ty = 1;

            string addToolTip = Environment.NewLine + "(prázdný údaj = bez omezení)";
            this._SearchKeyLabel = new DLabel() { Text = "KEYWORD", Bounds = new System.Drawing.Rectangle(xl, y, w, 18) }; y += this._SearchKeyLabel.Height + ly;
            object[] keywords = new object[] { KEYW_COWLEY, KEYW_INSTANCE, KEYW_SQL, KEYW_IOM, KEYW_WARNING, KEYW_ERROR, KEYW_DEADLOCK };
            this._SearchKeyCombo = new DComboBox() { Bounds = new System.Drawing.Rectangle(xc, y, w, 18), DropDownStyle = ComboBoxStyle.DropDown, FlatStyle = FlatStyle.Popup }; y += this._SearchKeyCombo.Height + ty;
            this._SearchKeyCombo.Items.AddRange(keywords);
            this.ToolTipForControl(this._SearchKeyCombo, "Vyberte nebo vepište hledaný text v údaji KEYWORD." + addToolTip);
            this._SearchKeyCombo.DComboBoxChanged += new DComboBoxChangedHandler(_KeyChanged);
            this.Controls.Add(this._SearchKeyCombo);
            this.Controls.Add(this._SearchKeyLabel);

            this._SearchTypeLabel = new DLabel() { Text = "Type", Bounds = new System.Drawing.Rectangle(xl, y, w, 18) }; y += this._SearchTypeLabel.Height + ly;
            this._SearchTypeText = new DTextBox() { Bounds = new System.Drawing.Rectangle(xc, y, w, 18) }; y += this._SearchTypeText.Height + ty;
            this.ToolTipForControl(this._SearchTypeText, "Vyhledat daný text v údaji TYPE." + addToolTip);
            this.Controls.Add(this._SearchTypeText);
            this.Controls.Add(this._SearchTypeLabel);

            this._SearchMethodLabel = new DLabel() { Text = "Method", Bounds = new System.Drawing.Rectangle(xl, y, w, 18) }; y += this._SearchMethodLabel.Height + ly;
            this._SearchMethodText = new DTextBox() { Bounds = new System.Drawing.Rectangle(xc, y, w, 18) }; y += this._SearchMethodText.Height + ty;
            this.ToolTipForControl(this._SearchMethodText, "Vyhledat daný text v údaji METHOD." + addToolTip);
            this.Controls.Add(this._SearchMethodText);
            this.Controls.Add(this._SearchMethodLabel);

            this._SearchDurationLabel = new DLabel() { Text = "Délka události (od - do, sekund)", Bounds = new System.Drawing.Rectangle(xl, y, w, 18) }; y += this._SearchDurationLabel.Height + ly;
            this._SearchDurationMinText = new DTextBox() { Bounds = new System.Drawing.Rectangle(xc, y, wh, 18) };
            this.ToolTipForControl(this._SearchDurationMinText, "Vyhledat události, jejichž doba (sekund) je rovna nebo VĚTŠÍ než daný údaj." + addToolTip);
            this.Controls.Add(this._SearchDurationMinText);
            this._SearchDurationMaxText = new DTextBox() { Bounds = new System.Drawing.Rectangle(xr, y, wh, 18) }; y += this._SearchDurationMinText.Height + ty;
            this.ToolTipForControl(this._SearchDurationMaxText, "Vyhledat události, jejichž doba (sekund) je rovna nebo MENŠÍ než daný údaj." + addToolTip);
            this.Controls.Add(this._SearchDurationMaxText);
            this.Controls.Add(this._SearchDurationLabel);

            this._SearchInTraceFilesCheck = new DCheckBox() { Text = "Prohledat i nenačtené soubory", Bounds = new System.Drawing.Rectangle(xc, y, w, 20) }; y += this._SearchInTraceFilesCheck.Height + ty;
            this.ToolTipForControl(this._SearchInTraceFilesCheck, "Umožní prohledat i obsah těch trace souborů, které dosud nebyly načteny do paměti.");
            this.Controls.Add(this._SearchInTraceFilesCheck);

            // this
            this.Size = new System.Drawing.Size(w, y);
            this.BackColor = Control.DefaultBackColor;
            this.BorderStyle = BorderStyle.None;
            this.Padding = new Padding(0);
            this.TabStop = true;

            // Tooltip style
            this.ToolTipForm.CurrentTooltipMode = TooltipMode.Baloon;
            this.ToolTipForm.ToolTipTitle = "Vyhledávání";
            this.ToolTipForm.ToolTipIcon = ToolTipIcon.Info;

        }
        private DLabel _SearchKeyLabel;
        private DComboBox _SearchKeyCombo;
        private DLabel _SearchTypeLabel;
        private DTextBox _SearchTypeText;
        private DLabel _SearchMethodLabel;
        private DTextBox _SearchMethodText;
        private DLabel _SearchDurationLabel;
        private DTextBox _SearchDurationMinText;
        private DTextBox _SearchDurationMaxText;
        private DCheckBox _SearchInTraceFilesCheck;
        #endregion
        #region Properties
        internal string ParamKeyword { get { return this._SearchKeyCombo.Text; } set { this._SearchKeyCombo.Text = value; } }
        internal string ParamType { get { return this._SearchTypeText.Text; } set { this._SearchTypeText.Text = value; } }
        internal string ParamMethod { get { return this._SearchMethodText.Text; } set { this._SearchMethodText.Text = value; } }
        internal string ParamTimeMin { get { return this._SearchDurationMinText.Text; } set { this._SearchDurationMinText.Text = value; } }
        internal string ParamTimeMax { get { return this._SearchDurationMaxText.Text; } set { this._SearchDurationMaxText.Text = value; } }
        internal bool ParamSearchInFiles { get { return this._SearchInTraceFilesCheck.Checked; } set { this._SearchInTraceFilesCheck.Checked = value; } }
        #endregion
        #region Eventy
        private void _KeyChanged(object sender, DComboBoxChangedArgs e)
        {
            this._ShowLabelForKeyword(this._SearchKeyCombo.Text);
        }
        private void _ShowLabelForKeyword(string keyword)
        {
            if (String.IsNullOrEmpty(keyword)) return;

            string addToolTip = Environment.NewLine + "(prázdný údaj = bez omezení)";
            string type = "Type";
            string typeTip = "Vyhledat daný text v údaji TYPE." + addToolTip;
            string method = "Method";
            string methodTip = "Vyhledat daný text v údaji METHOD." + addToolTip;
            keyword = keyword.Trim();
            switch (keyword)
            {
                case KEYW_COWLEY:
                    type = "Class number";
                    typeTip = "Vyhledat funkce pro danou třídu (vepište číslo třídy)." + addToolTip;
                    method = "Function name";
                    methodTip = "Vepište název funkce (action), nebo číslo funkce, nebo namespace (nebo část namespace)." + Environment.NewLine +
                        "Například zapsáním textu Lcs.Admin vyhledáte všechny funkce, které mají v namespace uvedený text." + Environment.NewLine +
                        "Při prohledávání souborů (nenačtené v paměti) je nutno zadat výhradně název (action)." + addToolTip;
                    break;
                case KEYW_INSTANCE:
                    type = "Instance type";
                    typeTip = "Vepište název instance bez namespace. Velikost písmen je ignorována." + addToolTip;
                    method = "Method name";
                    methodTip = "Vepište název metody bez závorek. Velikost písmen je ignorována." + addToolTip;
                    break;
                case KEYW_SQL:
                    type = "SPID";
                    typeTip = "Vepište číslo connection (SPID)." + addToolTip;
                    method = "SQL contain text";
                    methodTip = "Vepište hledaný text SQL příkazu. Bude vyhledán i uprostřed reálného SQL příkazu." + Environment.NewLine +
                        "Nelze zadat regulární výrazy. Velikost písmen je ignorována." + addToolTip;
                    break;
                case KEYW_IOM:
                    type = "Class number";
                    typeTip = "Vepište číslo třídy, jejíž IOM hledáte." + addToolTip;
                    method = "IOM number";
                    typeTip = "Vepište číslo IOM, který hledáte." + addToolTip;
                    break;
                case KEYW_WARNING:
                    type = "Keyword + Type + Method";
                    typeTip = "Vyhledat daný text v údaji KEYWORD, TYPE, METHOD." + addToolTip;
                    method = "User";
                    methodTip = "Vyhledat daný text v údaji USER (kterýkoli)." + addToolTip;
                    break;
                case KEYW_ERROR:
                    type = "User0";
                    typeTip = "Hledat události, které mají v poli User0 zadaný text." + addToolTip;
                    method = "User1";
                    methodTip = "Hledat události, které mají v poli User1 zadaný text." + addToolTip;
                    break;
                case KEYW_DEADLOCK:
                    type = "User0";
                    typeTip = "Hledat události, které mají v poli User0 zadaný text." + addToolTip;
                    method = "User1";
                    methodTip = "Hledat události, které mají v poli User1 zadaný text." + addToolTip;
                    break;
            }
            this._SearchTypeLabel.Text = type;
            this._SearchMethodLabel.Text = method;
            this.ToolTipForControl(this._SearchTypeText, typeTip);
            this.ToolTipForControl(this._SearchMethodText, methodTip);
        }
        #endregion
        #region IDataPersistent Members
        XElement IDataPersistent.PersistentData
        {
            get
            {
                return CreatePresetData(this.ParamKeyword, this.ParamType, this.ParamMethod, this.ParamTimeMin, this.ParamTimeMax, this.ParamSearchInFiles);
            }
            set
            {
                if (value != null)
                {
                    this.ParamKeyword = _GetAttributeString(value, "keyword");
                    this.ParamType = _GetAttributeString(value, "type");
                    this.ParamMethod = _GetAttributeString(value, "method");
                    this.ParamTimeMin = _GetAttributeString(value, "time_min");
                    this.ParamTimeMax = _GetAttributeString(value, "time_max");
                    this.ParamSearchInFiles = _GetAttributeBool(value, "search_file");
                }
            }
        }
        #endregion
        #region Static podpora, konstanty
        /// <summary>
        /// Vrátí XElement pro konkrétní událost
        /// </summary>
        /// <param name="eventItem"></param>
        /// <returns></returns>
        internal static XElement CreatePresetData(EventItem eventItem)
        {
            int userLength = eventItem.UserLength;
            string keyword = eventItem.Keyword;
            string type = eventItem.Type;
            string method = eventItem.Method;
            string timeMin = eventItem.Time.Seconds.ToString();
            string timeMax = "";
            keyword = keyword.Trim().ToUpper();
            switch (keyword)
            {
                case "COWLEY":
                    EventCowleyItem cwl = eventItem as EventCowleyItem;
                    type = (cwl == null ? "" : cwl.CowleyClassNumber.ToString());
                    method = (cwl == null ? "" : cwl.CowleyActionName);
                    timeMin = cwl.ElementTime.Seconds.ToString();
                    break;
                case "INSTANCE":
                    break;
                case "SQL":
                    EventSqlItem sql = eventItem as EventSqlItem;
                    type = (sql == null ? "" : sql.SqlPId.ToString());
                    method = (sql == null ? "" : sql.SqlCommand);
                    break;
                case "RECORDRULES":
                    EventPairItem iom = eventItem as EventPairItem;
                    type = (userLength >= 2 ? eventItem.UserData(1) : "");    // "Class number";
                    method = (userLength >= 1 ? eventItem.UserData(0) : "");  // "IOM number";
                    break;
            }
            return CreatePresetData(keyword, type, method, timeMin, timeMax, null);
        }
        /// <summary>
        /// Vrátí XElement pro explicitní data
        /// </summary>
        /// <param name="keyword"></param>
        /// <param name="type"></param>
        /// <param name="method"></param>
        /// <param name="timeMin"></param>
        /// <param name="timeMax"></param>
        /// <returns></returns>
        internal static XElement CreatePresetData(string keyword, string type, string method, string timeMin, string timeMax, bool? searchFile)
        {
            XElement xElement = new XElement("values");
            xElement.Add(new XAttribute("keyword", keyword));
            xElement.Add(new XAttribute("type", type));
            xElement.Add(new XAttribute("method", method));
            xElement.Add(new XAttribute("time_min", timeMin));
            xElement.Add(new XAttribute("time_max", timeMax));
            if (searchFile.HasValue)
                xElement.Add(new XAttribute("search_file", (searchFile.Value ? "A" : "N")));
            return xElement;
        }
        private static string _GetAttributeString(XElement element, string name)
        {
            XAttribute attr = element.Attribute(name);
            if (attr == null) return "";
            return attr.Value;
        }
        private bool _GetAttributeBool(XElement element, string name)
        {
            XAttribute attr = element.Attribute(name);
            if (attr == null || String.IsNullOrEmpty(attr.Value)) return false;
            return (attr.Value == "A");
        }

        internal const string KEYW_COWLEY = "COWLEY";
        internal const string KEYW_INSTANCE = "INSTANCE";
        internal const string KEYW_SQL = "SQL";
        internal const string KEYW_IOM = "RECORDRULES";
        internal const string KEYW_WARNING = "Sys.Warnings";
        internal const string KEYW_ERROR = "Sys.Errors";
        internal const string KEYW_DEADLOCK = "Sys.Deadlocks";
        #endregion
    }
}
